//
// Created By: Xiaofeng.Gu 2021/04/22
//
#include "cybertron/core/ConfigureClient.hpp"
#include "cybertron/core/UtilUrlRequest.hpp"
#include "cybertron/core/JsonReader.hpp"
#include "cybertron/core/UtilTime.hpp"
#include "cybertron/core/Log.hpp"
#include "cybertron/core/SimOneEnv.hpp"
#include <thread>

#define CODE_STRING "code"
#define MESSAGE_STRING "message"
#define MODULE_STRING "module"
#define PARAMETERS_STRING "parameters"
#define NAME_STRING "name"
#define VALUE_STRING "value"
#define KEY_STRING "key"
#define TTL_STRING "ttl"
#define DURATION_SECONDS 1



CYBERTRON_BEGIN


CybertronCoreAPI std::shared_ptr<ConfigureClient> gConfigureClient = nullptr;

CybertronCoreAPI void createConfigureClient()
{
	if (gConfigureClient == nullptr)
	{
		static std::shared_ptr<ConfigureClient> configureClient = std::make_shared<ConfigureClient>();
		gConfigureClient = configureClient;

		//SimOneEnv::GetInstance().Init();
		SimOneEnv::SimOneConfigCenterInfo configCenterInfo;
		SimOneEnv::GetInstance().GetConfigCenterInfo(configCenterInfo);

		gConfigureClient->Initialize(configCenterInfo.ConfigCenterAddr, std::to_string(configCenterInfo.ConfigCenterPort));
	}
}

CybertronCoreAPI bool setupConfigureClient(
	std::vector<std::string>& modules,
	std::string& error,
	std::map<std::string, std::map<std::string, std::string>>& moduleParamValueMap)
{
	createConfigureClient();

	return gConfigureClient->InitModule(modules, error, moduleParamValueMap);
}

CybertronCoreAPI bool setupConfigureClient(
	const std::string& module,
	std::string& error,
	std::map<std::string, std::string>& paramValueMap)
{
	createConfigureClient();

	std::vector<std::string> modules;
	modules.push_back(module);

	std::map<std::string, std::map<std::string, std::string>> moduleParamValueMap;
	bool succeed = setupConfigureClient(modules, error, moduleParamValueMap);
	if (moduleParamValueMap.find(module) != moduleParamValueMap.end())
	{
		paramValueMap = moduleParamValueMap[module];
		return true;
	}

	return false;
}

ConfigureClient::ConfigureClient()
{
	mCenterServerIp = "127.0.0.1";
	mServerPort = "100000";
	mModuleParamValueMap.clear();
}

bool ConfigureClient::SetConfigure(std::string& configure, const std::string& module, std::string& error)
{
	nlohmann::json configureJson;
	bool succeed = JsonReader::loadString(configureJson, configure);
	if (succeed == false)
	{
		return false;
	}

	if (JsonReader::hasValue(configureJson, CODE_STRING) == false ||
		JsonReader::getString(configureJson, CODE_STRING) == "error")
	{
		if (JsonReader::hasValue(configureJson, MESSAGE_STRING) == false)
		{
			return false;
		}
		const nlohmann::json& message = JsonReader::getChild(configureJson, MESSAGE_STRING);
		error = JsonReader::getString(message, MESSAGE_STRING);

		return false;
	}

	if (JsonReader::hasValue(configureJson, MESSAGE_STRING) == false)
	{
		return false;
	}
	const nlohmann::json& message = JsonReader::getChild(configureJson, MESSAGE_STRING);

	if (JsonReader::hasValue(message, MODULE_STRING) == false)
	{
		return false;
	}
	const nlohmann::json& moduleJson = JsonReader::getChild(message, MODULE_STRING);
	std::string moduleString = JsonReader::getString(moduleJson);
	if (moduleString != module)
	{
		error = "invalid module";
		return false;
	}

	const nlohmann::json& parametersJson = JsonReader::getChild(message, PARAMETERS_STRING);

	std::unique_lock<std::mutex> statesLock(mMutex);

	std::map<std::string, std::string> paramValueMap;
	for (int index = 0; index < parametersJson.size(); index++)
	{
		nlohmann::json parameter = parametersJson[index];

		if (JsonReader::hasValue(parameter, NAME_STRING) && JsonReader::hasValue(parameter, VALUE_STRING))
		{
			const nlohmann::json& paramJson = JsonReader::getChild(parameter, NAME_STRING);
			const nlohmann::json& valueJson = JsonReader::getChild(parameter, VALUE_STRING);
			std::string param = JsonReader::getString(paramJson);
			std::string value = JsonReader::getString(valueJson);
			paramValueMap[param] = value;
		}
	}
	mModuleParamValueMap[module] = paramValueMap;

	return true;
}

bool ConfigureClient::SetValue(std::string& valueString, const std::string& module, const std::string& param, std::string& error)
{
	nlohmann::json valueJson;
	bool succeed = JsonReader::loadString(valueJson, valueString);
	if (succeed == false)
	{
		return false;
	}

	if (JsonReader::hasValue(valueJson, CODE_STRING) == false ||
		JsonReader::getString(valueJson, CODE_STRING) == "error")
	{
		if (JsonReader::hasValue(valueJson, MESSAGE_STRING) == false)
		{
			return false;
		}
		const nlohmann::json& message = JsonReader::getChild(valueJson, MESSAGE_STRING);
		error = JsonReader::getString(message, MESSAGE_STRING);

		return false;
	}

	if (JsonReader::hasValue(valueJson, MESSAGE_STRING) == false)
	{
		return false;
	}
	const nlohmann::json& message = JsonReader::getChild(valueJson, MESSAGE_STRING);

	if (JsonReader::hasValue(message, VALUE_STRING) == false)
	{
		return false;
	}
	const nlohmann::json& json = JsonReader::getChild(message, VALUE_STRING);
	std::string value = JsonReader::getString(json);

	std::unique_lock<std::mutex> statesLock(mMutex);
	std::map<std::string, std::map<std::string, std::string>>::iterator it = mModuleParamValueMap.find(module);
	if (it == mModuleParamValueMap.end())
	{
		mModuleParamValueMap[module] = std::map<std::string, std::string>();
	}
	mModuleParamValueMap[module][param] = value;

	return true;
}

CybertronCoreAPI void ConfigureClient::Initialize(const std::string& cccIp, const std::string& cccPort)
{
	mCenterServerIp = cccIp;
	mServerPort = cccPort;
}

CybertronCoreAPI bool ConfigureClient::InitModule(
	std::vector<std::string>& modules,
	std::string& error,
	std::map<std::string, std::map<std::string, std::string>>& moduleParamValueMap)
{
	for (size_t index = 0; index < modules.size(); index ++)
	{
		std::string thisModule = modules[index];

		std::string getUrl = "http://" + mCenterServerIp + ":" + mServerPort + "/parameter/" + thisModule;
		std::string configure;

		bool succeed = GetUrl(getUrl, configure, error);
		if (succeed == false)
		{
			return false;
		}

		if (SetConfigure(configure, thisModule, error) == false)
		{
			return false;
		}
	}

	std::unique_lock<std::mutex> statesLock(mMutex);
	moduleParamValueMap = mModuleParamValueMap;

	return true;
}

CybertronCoreAPI bool ConfigureClient::GetValue(const std::string& module, const std::string& param, std::string& value, std::string& error)
{
	std::string getUrl = "http://" + mCenterServerIp + ":" + mServerPort + "/parameter/" + module + "/" + param;
	std::string valueString;

	bool succeed = GetUrl(getUrl, valueString, error);
	if (succeed == false)
	{
		return false;
	}

	succeed = SetValue(valueString, module, param, error);
	if (succeed == false)
	{
		return false;
	}

	std::unique_lock<std::mutex> statesLock(mMutex);

	std::map<std::string, std::map<std::string, std::string>>::iterator it = mModuleParamValueMap.find(module);
	if (it == mModuleParamValueMap.end())
	{
		return false;
	}

	std::map<std::string, std::string>::iterator itParam = it->second.find(param);
	if (itParam == it->second.end())
	{
		return false;
	}
	value = itParam->second;

	return true;
}

//ttl(Time To Live) unit is second.
CybertronCoreAPI bool ConfigureClient::SetCache(const std::string & key, const std::string & value, int ttl, std::string &error)
{
    if (ttl < 0) {
        error = "ttl must be >= 0";
        return false;
    }

	std::string url = "http://" + mCenterServerIp + ":" + mServerPort + "/cache/set";
	std::string valueString;

    nlohmann::json reqJson;
    reqJson[KEY_STRING] = key;
    reqJson[VALUE_STRING] = value;
    reqJson[TTL_STRING] = ttl;

    try {
        valueString = UtilUrlRequest::PostUrl(url, reqJson.dump());
    } catch (const UtilUrlRequest::TException& ex) {
        error = ex.what();
        return false;
    }

    nlohmann::json respJson;
    bool succeed = JsonReader::loadString(respJson, valueString);
    if (succeed == false)
    {
        return false;
    }

    if (JsonReader::hasValue(respJson, CODE_STRING) == false ||
        JsonReader::getString(respJson, CODE_STRING) == "error")
    {
        if (JsonReader::hasValue(respJson, MESSAGE_STRING) == false)
        {
            return false;
        }
        error = JsonReader::getString(respJson, MESSAGE_STRING);

        return false;
    }
    return true;
}

CybertronCoreAPI bool ConfigureClient::GetCache(const std::string & key, std::string & value, std::string & error)
{
	std::string url = "http://" + mCenterServerIp + ":" + mServerPort + "/cache/get?key=" + key;
	std::string valueString;

    try
    {
        valueString = UtilUrlRequest::GetUrl(url);
    }
    catch (const UtilUrlRequest::TException& ex)
    {
        error = ex.what();
		return false;
    }

    nlohmann::json respJson;
    bool succeed = JsonReader::loadString(respJson, valueString);
    if (succeed == false)
    {
        return false;
    }

    if (JsonReader::hasValue(respJson, CODE_STRING) == false ||
        JsonReader::getString(respJson, CODE_STRING) == "error")
    {
        if (JsonReader::hasValue(respJson, MESSAGE_STRING) == false)
        {
            return false;
        }
        error = JsonReader::getString(respJson, MESSAGE_STRING);

        return false;
    }

    if (JsonReader::hasValue(respJson, MESSAGE_STRING) == false)
    {
        return false;
    }
    value = JsonReader::getString(respJson, MESSAGE_STRING);
    return true;
}

bool ConfigureClient::GetUrl(const std::string& url, std::string& result, std::string& error)
{
	bool succeed = true;
	UtilTime::millisecondsUpdate();
	unsigned int now = UtilTime::millisecondsCurrent();
	while (true)
	{
		try
		{
			result = UtilUrlRequest::GetUrl(url);
			succeed = true;
			break;
		}
		catch (const UtilUrlRequest::TException& ex)
		{
			error = ex.what();
			succeed = false;
		}

		UtilTime::millisecondsUpdate();
		if ((UtilTime::millisecondsCurrent() - now) / 1000 > DURATION_SECONDS)
		{
			break;
		}

		std::this_thread::sleep_for(std::chrono::milliseconds(1000));
		logInfo(">>>>>>>>>>>>>>>>>connecting %s:%s\n", mCenterServerIp.c_str(), mServerPort.c_str());
	}

	if (succeed == false)
	{
		logInfo(">>>>>>>>>>>>>>>>>fail to connect %s:%s\n", mCenterServerIp.c_str(), mServerPort.c_str());
	}

	return succeed;
}

CybertronCoreAPI bool ConfigureClient::GetConfigure(const std::string& module, std::map<std::string, std::string>& paramValueMap, std::string& error)
{
	std::string getUrl = "http://" + mCenterServerIp + ":" + mServerPort + "/parameter/" + module;
	std::string configure;

	bool succeed = GetUrl(getUrl, configure, error);
	if (succeed == false)
	{
		return false;
	}

	succeed = SetConfigure(configure, module, error);
	if (succeed == false)
	{
		return false;
	}

	std::unique_lock<std::mutex> statesLock(mMutex);
	std::map<std::string, std::map<std::string, std::string>>::iterator it = mModuleParamValueMap.find(module);
	if (it == mModuleParamValueMap.end())
	{
		return false;
	}
	paramValueMap = it->second;

	return true;
}


bool cccGetConfigureFunctionImpl(const std::string& module, std::map<std::string, std::string>& paramValueMap, std::string& error)
{
	if (gConfigureClient == nullptr) {
		return false;
	}
	return gConfigureClient->GetConfigure(module, paramValueMap, error);
}

bool cccGetValueFunctionImpl(const std::string& module, const std::string& param, std::string& value, std::string& error)
{
	if (gConfigureClient == nullptr) {
		return false;
	}

	return gConfigureClient->GetValue(module, param, value, error);
}


CYBERTRON_END